﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ciloci.Flee;
using System.ComponentModel;
using AppFx.Reflection;

namespace AppFx.Interpretation
{
	/// <summary>
	/// Ein Evaluator, welcher Ausdrücke mit Hilfe der Flee-Library (LGPL) auswertet. (Siehe Readme.txt bzw. License.txt)
	/// </summary>
	/// <typeparam name="T">Rückgabewert der Evaluierungen.</typeparam>
	public class FleeEvaluator<T> : IEvaluator<T>
	{
		#region Properties

		/// <summary>
		/// Der bereits kompilierte Ausdruck.
		/// </summary>
		/// <value>
		/// Die vorkompilierte IGenericExpression.
		/// </value>
		public virtual IGenericExpression<T> Expression { get; protected set; }

		/// <summary>
		/// Eine Liste aller vorhandenen Indizes/Spalten.
		/// </summary>
		/// <value>
		/// Eine Liste von PropertyDescriptors/Indizes/Spalten.
		/// </value>
		public virtual IList<PropertyDescriptor> Indices { get; protected set; }

		/// <summary>
		/// Das aktuell auszuwertende Objekt/Datenkontext.
		/// </summary>
		protected virtual object CurrentItem { get; private set; }

		/// <summary>
		/// Das Listenelement, welches alle auszuwertenden Objekte beinhaltet und Indizes zur Verfügung stellt.
		/// </summary>
		/// <value>
		/// Die IIndexList, welche alle Daten und Indizes enthält.
		/// </value>
		public IIndexList Context { get; set; }

		#endregion

		#region Constructors

		/// <summary>
		/// Erstellt eine neue Instant der <see cref="FleeEvaluator&lt;T&gt;"/>-Klasse.
		/// </summary>
		public FleeEvaluator() { }

		#endregion

		#region Methods

		/// <summary>
		/// Analysiert und kompiliert den übergebenen Ausdruck und entnimmt zur Verfügung stehende Variablen dem Beispielobjekt 'item'.
		/// </summary>
		/// <param name="expression">Der zu analysierende Ausdruck.</param>
		/// <param name="item">Das Beispielobjekt, welchem die zur Verfügung stehenden Variablen entnommen werden sollen.</param>
		public void Parse(string expression, object item)
		{
			Indices = Context.GetIndices();

			expression = Optimize(expression);
			ExpressionContext context = new ExpressionContext(item);
			context.Imports.AddType(typeof(Math.ListCalculator));

			if (Expression != null)
			{
				context.Variables.ResolveVariableType -= new EventHandler<ResolveVariableTypeEventArgs>(Variables_ResolveVariableType);
				context.Variables.ResolveVariableValue -= new EventHandler<ResolveVariableValueEventArgs>(Variables_ResolveVariableValue);
			}

			context.Variables.ResolveVariableType += new EventHandler<ResolveVariableTypeEventArgs>(Variables_ResolveVariableType);
			context.Variables.ResolveVariableValue += new EventHandler<ResolveVariableValueEventArgs>(Variables_ResolveVariableValue);

			Expression = context.CompileGeneric<T>(expression);
		}

		/// <summary>
		/// Optimiert den übergebenen Ausdruck und korrigiert DisplayNames in Names.
		/// </summary>
		/// <param name="expression">Der zu optimierende Ausdruck.</param>
		/// <returns>Den optimierten Ausdruck.</returns>
		protected virtual string Optimize(string expression)
		{
			string originalExpression = expression;
			foreach (var index in Indices)
			{
				if (expression.Contains(index.DisplayName))
					expression = expression.Replace(index.DisplayName, index.Name);
			}
			return expression;
		}

		/// <summary>
		/// Wertet den zuvor geparsten Ausdruck eines Objekts/Listenelements anhand des übergebenen Elements aus.
		/// </summary>
		/// <param name="item">Listenelement, welches die benötigten Daten zur Verfügung stellt.</param>
		/// <returns>Das Ergebnis der Auswertung.</returns>
		public T Evaluate(object item)
		{
			if (Expression == null)
				throw new NullReferenceException("FleeFilterEvaluator.Expression must not be null.");
			CurrentItem = item;
			Expression.Owner = item;
			return Expression.Evaluate();
		}

		#endregion

		#region EventHandlers

		// Typinformationen zu unbekannten Variablen nachschlagen.
		private void Variables_ResolveVariableType(object sender, ResolveVariableTypeEventArgs e)
		{
			// Listtype?
			if (e.VariableName.EndsWith("_s"))
			{
				var matches = Indices.Where(index => index.Name + "_s" == e.VariableName);
				switch (matches.Count())
				{
					case 1:
						e.VariableType = CreateListType(e, matches.Single());
						break;
					default:
						// ask someone else...
						break;
				}
			}
		}

		// Variablenwerte nachschlagen.
		private void Variables_ResolveVariableValue(object sender, ResolveVariableValueEventArgs e)
		{
			// Listtype?
			if (e.VariableName.EndsWith("_s"))
			{
				var matches = Indices.Where(index => index.Name + "_s" == e.VariableName);
				switch (matches.Count())
				{
					case 1:
						e.VariableValue = CreateList(matches.Single());
						break;
					default:
						// ask someone else...
						break;
				}
			}
		}

		// Gibt den Typ einer Liste zurück.
		private Type CreateListType(ResolveVariableTypeEventArgs e, PropertyDescriptor descriptor)
		{
			if (descriptor.PropertyType == typeof(int))
			{
				return typeof(List<int>);
			}
			else if (descriptor.PropertyType == typeof(double))
			{
				return typeof(List<double>);
			}
			else
				return null;
		}

		// Füllt eine Liste.
		private object CreateList(PropertyDescriptor desciptor)
		{
			if (desciptor.PropertyType == typeof(int))
			{
				List<int> list = new List<int>();
				foreach (var item in Context)
					list.Add((int)desciptor.GetValue(item));
				return list;
			}
			else if (desciptor.PropertyType == typeof(double))
			{
				List<double> list = new List<double>();
				foreach (var item in Context)
					list.Add((double)desciptor.GetValue(item));
				return list;
			}
			else
				return null;
		}

		#endregion
	}
}
